#include "stdafx.h"
#include "Sketch.h"

// Catmull-Rom spline basis functions
double cr_b1(double t){ return 0.5*(t*(-1+t*(2.0-t)));}
double cr_b2(double t){ return 0.5*(2.0+t*t*(3.0*t-5.0));}
double cr_b3(double t){ return 0.5*(t*(1+t*(4.0-3.0*t)));}
double cr_b4(double t){ return 0.5*(t*t*(t-1.0));}

// Hermite curve basis functions
double h_b1(double t){ return 1+t*t*(2.0*t-3.0);}
double h_b2(double t){ return t*t*(3.0-2.0*t);}
double h_b3(double t){ return t*(1.0+t*(t-2.0));}
double h_b4(double t){ return t*t*(t-1.0);}

CSketch::CSketch(void)
{
}

CSketch::CSketch(int nPixels, int p[][2], GLuint startT, GLuint sketchT, int area_width, int area_height)
{
	startTime  = startT;
	sketchTime = sketchT;

	max_height = -9999;
	min_height =  9999;

	sketch_area_width = area_width;
	sketch_area_height = area_height;

	for(int i=0; i < nPixels; i++)
	{
		int x = p[i][0];
		int y = p[i][1];

		sketchPixels.push_back(new CVec2di(x, y));

		if (y > max_height)
		{
			max_height = y;
		}
		if (y < min_height)
		{
			min_height = y;
		}
	}
	computeRawControlPoints();
	closed = isClosed();
}

CSketch::CSketch(CSketch* sketch)
{
	this->startTime  = sketch->startTime;
	this->sketchTime = sketch->sketchTime;

	for (int i=0; i < sketch->sketchPixels.size(); i++)
		this->sketchPixels.push_back(sketch->sketchPixels.at(i));
	
	for (int i=0; i < sketch->rawControlPoints.size(); i++)
		this->getRawControlPoints()->push_back(sketch->getRawControlPoints()->at(i));

	for (int i=0; i < sketch->unitControlPoints.size(); i++)
		this->getUnitControlPoints()->push_back(sketch->getUnitControlPoints()->at(i));
	
	this->innerControlPointRatio = sketch->innerControlPointRatio;
	this->sketchPixels = sketch->sketchPixels;

	this->max_height = sketch->max_height;
	this->min_height = sketch->min_height;
}

CSketch::~CSketch(void)
{
}

void CSketch::display2D(RenderModeType renderMode)
{
	glEnable(GL_BLEND);
	
	glLineWidth(3);
	
	if(renderMode==RAW_POINTS){
		if(sketchPixels.size()>0){
			
			glPointSize(1.0);
			glBegin(GL_POINTS);
			for (int i=0; i<sketchPixels.size(); i++)
				glVertex2iv(sketchPixels[i]->getArray());
			glEnd();
		}
	} else if(renderMode==CONTROL_POINTS){
		if(rawControlPoints.size()>0){ 
			glPointSize(3.0);
			glBegin(GL_POINTS);
			for(int i=0; i<rawControlPoints.size(); i++)
				glVertex2iv((*rawControlPoints[i]).getArray());
			glEnd();
		}
	} else if(renderMode==POLYLINE){
		if(rawControlPoints.size()>0){
			if(isClosed()) glBegin(GL_LINE_LOOP);
			else glBegin(GL_LINE_STRIP);
			for(int i=0; i<rawControlPoints.size(); i++)
				glVertex2iv((*rawControlPoints[i]).getArray());
			glEnd();
		}
	} else if(renderMode==CATMULL_ROM){
		_drawCatmullRomSpline();
	}
	glDisable(GL_BLEND);
}

bool CSketch::isClosed()
{
	return innerControlPointRatio <= 0.5 && (*rawControlPoints.front()-*rawControlPoints.back()).length()<50;
}

void CSketch::_drawCatmullRomSpline()
{
	int i,j;
	CVec2df r0,r1;
	CVec2df q;
	float t;
	int NUM_SAMPLES=10; // number of points used to approximate spline curve segment
	
	if(rawControlPoints.size()==2){
		glBegin(GL_LINE_STRIP);
		for(j=0;j<rawControlPoints.size();j++) 
			glVertex2iv((*rawControlPoints[j]).getArray());
		glEnd();
	}
	else if(isClosed() && rawControlPoints.size()>2){
		// Since the last and first controlpoint are connected we have numControlPoints curve segments
		// Segment i is defined by the points p_(i-1), p_i, p_(i+1), p_(i_2)
		for(i=rawControlPoints.size(); i<2*rawControlPoints.size();i++){
			// for each curve segment draw a polyline with NUM_SAMPLES line segments
			glBegin(GL_LINE_STRIP);
			for(j=0;j<=NUM_SAMPLES;j++){
				t=((float) j)/NUM_SAMPLES;
				int numPoints = rawControlPoints.size();
				q[X]=cr_b1(t) * rawControlPoints[(i-1) % numPoints]->v[X]
				    +cr_b2(t) * rawControlPoints[ i    % numPoints]->v[X]
				    +cr_b3(t) * rawControlPoints[(i+1) % numPoints]->v[X]
				    +cr_b4(t) * rawControlPoints[(i+2) % numPoints]->v[X];
				
				q[Y]=cr_b1(t) * rawControlPoints[(i-1) % numPoints]->v[Y]
					+cr_b2(t) * rawControlPoints[ i    % numPoints]->v[Y]
					+cr_b3(t) * rawControlPoints[(i+1) % numPoints]->v[Y]
					+cr_b4(t) * rawControlPoints[(i+2) % numPoints]->v[Y];
				glVertex2fv(q.getArray());
			}
			glEnd();
		}	
	} else if (rawControlPoints.size()>1){
		// N control points give N-1 curve segments
		//
		// First curve segment is a Hermite curve with start tangent 
		// equal to the vector from ther first to the second control point
		//printf("%d _ %d\n", *controlPoints[1], *controlPoints.at(1))[1]);
		r0[X] = rawControlPoints[1]->v[X] - rawControlPoints[0]->v[X];
		r0[Y] = rawControlPoints[1]->v[Y] - rawControlPoints[0]->v[Y];
		r1[X] = 0.5*(rawControlPoints[2]->v[X] - rawControlPoints[0]->v[X]);
		r1[Y] = 0.5*(rawControlPoints[2]->v[Y] - rawControlPoints[0]->v[Y]);
		glBegin(GL_LINE_STRIP);
		for(j=0;j<=NUM_SAMPLES;j++){
			t=((float) j)/NUM_SAMPLES;
			q[X]=h_b1(t) * rawControlPoints[0]->v[X] + h_b2(t) * rawControlPoints[1]->v[X] + h_b3(t) * r0.v[X] + h_b4(t) * r1.v[X];
			q[Y]=h_b1(t) * rawControlPoints[0]->v[Y] + h_b2(t) * rawControlPoints[1]->v[Y] + h_b3(t) * r0.v[Y] + h_b4(t) * r1.v[Y];
			glVertex2fv(q.getArray());
		}
		glEnd();
		
		// The N-3 curve segments in the middle (i.e. without first and last segment)
		// are normal Catmull-Rom spline segments
		// Segment i is defined by the points p_(i-1), p_i, p_(i+1), p_(i_2)
		for(i=1;i<(rawControlPoints.size()-2);i++){
			// for each curve segment draw a polyline with NUM_SAMPLES line segments
			glBegin(GL_LINE_STRIP);
			for(j=0;j<=NUM_SAMPLES;j++){
				t=((float) j)/NUM_SAMPLES;
				q[X]=cr_b1(t) * rawControlPoints[i-1]->v[X] + cr_b2(t) * rawControlPoints[i]->v[X]
					+cr_b3(t) * rawControlPoints[i+1]->v[X] + cr_b4(t) * rawControlPoints[i+2]->v[X];
				q[Y]=cr_b1(t) * rawControlPoints[i-1]->v[Y] + cr_b2(t) * rawControlPoints[i]->v[Y]
				    +cr_b3(t) * rawControlPoints[i+1]->v[Y] + cr_b4(t) * rawControlPoints[i+2]->v[Y];
				glVertex2fv(q.getArray());
			}
			glEnd();
		}	
		
		// Last curve segment is a Hermite curve with start tangent 
		// equal to vector from firts to the second point
		r0[X] = rawControlPoints[rawControlPoints.size()-1]->v[X] - rawControlPoints[rawControlPoints.size()-3]->v[X];
		r0[Y] = rawControlPoints[rawControlPoints.size()-1]->v[Y] - rawControlPoints[rawControlPoints.size()-3]->v[Y];
		r1[X] = 0.5*(rawControlPoints[rawControlPoints.size()-1]->v[X] - rawControlPoints[rawControlPoints.size()-2]->v[X]);
		r1[Y] = 0.5*(rawControlPoints[rawControlPoints.size()-1]->v[Y] - rawControlPoints[rawControlPoints.size()-2]->v[Y]);
		glBegin(GL_LINE_STRIP);
		for(j=0;j<=NUM_SAMPLES;j++){
			t=((float) j)/NUM_SAMPLES;
			q[X]=h_b1(t) * rawControlPoints[rawControlPoints.size()-2]->v[X] 
			    +h_b2(t) * rawControlPoints[rawControlPoints.size()-1]->v[X]
			    +h_b3(t) * r0[X] 
			    +h_b4(t) * r1[X];
			q[Y]=h_b1(t) * rawControlPoints[rawControlPoints.size()-2]->v[Y] 
				+h_b2(t) * rawControlPoints[rawControlPoints.size()-1]->v[Y]
			    +h_b3(t) * r0[Y] 
			    +h_b4(t) * r1[Y];
			glVertex2fv(q.getArray());
		}
		glEnd();
	}
}

void CSketch::computeRawControlPoints()
{
	// Computes from list on input pixels a subset of points which can be used
	// as input for subsequent smoothing operations
	if (sketchPixels.size()==0) return;
	rawControlPoints.clear();
	
	// Iterate input pixels and only use points which are at least MIN_PIXEL_SPACING pixels apart
	// Always use first and last point (ie. can have less than MIN_PIXEL_SPACING at the beginning and end)
	const int MIN_PIXEL_SPACING=4;
	int i;
	rawControlPoints.push_back(sketchPixels[0]);
	for(i=1; i<(sketchPixels.size()-1); i++){
		if((*sketchPixels[i] - *rawControlPoints.back()).length() >= MIN_PIXEL_SPACING){
			rawControlPoints.push_back(sketchPixels[i]);
		}
	}
	
	if(sketchPixels.back()!=rawControlPoints.back()){
		rawControlPoints.push_back(sketchPixels.back());
	}
	 
	if(rawControlPoints.size() > 1)
	{
		vector<CVec2di*>* output = new vector<CVec2di*>();
		resamplePoints(&rawControlPoints, output);
		
		rawControlPoints.clear();
		for (i = 0; i < output->size();i++) {
			rawControlPoints.push_back(output->at(i));
		}
		delete output;
	}
	computeInnerControlPointRatio();
	computeUnitControlPoints();
}

void CSketch::computeUnitControlPoints()
{
	// Translates control points from the sketch window coordinates to a unit vector
	if (rawControlPoints.size()==0) return;
	unitControlPoints.clear();
	
	for(int i=0; i<(rawControlPoints.size()); i++)
	{
		float unitX = (float) (*rawControlPoints.at(i))[0];
		float unitY = (float) (*rawControlPoints.at(i))[1];
		unitControlPoints.push_back(new CVec2df(unitX/(float)sketch_area_width, unitY/(float)sketch_area_height));
	}
}

void CSketch::computeInnerControlPointRatio()
{
	//line between start and end
	CVec2di* start = rawControlPoints.front();
	CVec2di* end   = rawControlPoints.back();
	
	int count = 0;
	for (int i = 1; i < rawControlPoints.size()-1; i++) {
		if(internalPoint(rawControlPoints.at(i), start, end))
		{
			count++;
		}
	}
	//compute number of control points within
	if(rawControlPoints.size() <= 2)
		innerControlPointRatio = 1.0;
	else {
		innerControlPointRatio = (double)count/(double)(rawControlPoints.size()-2);
	}
}

template <typename T> void CSketch::resamplePoints(vector<CVec2d<typename T>*>* input, vector<CVec2d<typename T>*>* output)
{
	vector<CVec2d<T>*> points;
	lineFit(input, &points);
	output->push_back(points[0]);
	for(int i = 1; i < points.size(); i++)
	{
		if((*points[i-1])[0] != (*points[i])[0] || (*points[i-1])[1] != (*points[i])[0])
		{
			double leftLength = lineLength(points[i-1], points[i]);
			if(!(leftLength < 10))
				output->push_back(points[i]);
		}
	}
	//delete points;
}
